iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
Mobile Development

Senior 工程師的入門指南:Flutter 進擊之路系列 第 10 篇

Day 10:燚!揭秘 Source Gen|Flutter 代碼生成

  • 分享至 

  • xImage
  •  

在上一篇文章中,我們深入探索了 Flutter 中的 Metadata,介紹了許多有趣的 Annotation。當然,光是了解 Metadata 只是冰山一角,今天我們要繼續深挖,帶大家認識 Flutter 中另一個強大的工具—— source_gen。

引言:

哈囉!各位 Flutter 愛好者,還記得我們上次提到,要讓自己的 Annotation 產生實際的影響,需要配合 GeneratorForAnnotation 嗎?那麼這次,我們就來深入瞭解這個與其相關的 library - source_gen,它如何協助我們自動生成代碼,以及其強大的應用場景。

首先,讓我們理解 source_gen 是什麼。它是 Dart 的一個代碼生成工具,並非 Flutter 獨有,主要用於構建 Annotations 和生成器。它可以自動為我們生成常見的模板代碼,減少手動編寫的冗餘。

如何運作

當我們為某個特定的類或函數添加 Annotation 後,source_gen 會基於這些 Annotation 和相對應的生成器,自動為我們產生相應的程式碼。例如,有了 json_serializable,我們就不需要手動編寫 JSON 的序列化和反序列化代碼了,這一切都可以由 source_gen 幫我們完成。那接下來我們就來自己動手寫看看自動化代碼生成吧!

基本的使用

要使用 source_gen,我們首先需要在 pubspec.yaml 中添加相應的依賴。如果只是自己開發使用可以放在 dev_dependencies,如果是要寫成 package 提供給其他人用,記得放在 dependencies 哦!

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  build_runner: ^2.0.0
  source_gen: ^1.0.0

接著,在你的 Dart 檔案中新增 Annotations,如同上一篇談到的 Annotation 可以是任何一個 class,這裡的命名可以自己決定,我們就先取最簡單的 TestMetadata。

class TestMetaData {
  const TestMetaData();
}

再來寫一個 test.dart,他會被加上 Annotation ,並在稍後被加上生成的程式碼,作為這次測試的對象

import 'package:flutter_day_10_build_runner/test_annotation.dart';

@TestMetaData("doraralab")
class MyClass {}

完成 Annotation 後,我們接下來寫最重要的部分,如何解析這個 Annotation。首先新增一個 TestGenerator 的檔案,這裡需要 extends GeneratorForAnnotation,用來告訴 build runner 我們要解析的 Annotation 是什麼 ,並且應該要如何生成程式碼。

當中 generateForAnnotatedElement 是必須實作的函數,裡面包含三個參數 element, annotation, buildStep,稍後會再來講解他們有什麼作用。現在只關注於他最後產生的結果:生成 class TestMetaDataFromGenerator{}的程式碼。

import 'package:flutter_day_10_build_runner/test_annotation.dart';
import 'package:source_gen/source_gen.dart';

class TestGenerator extends GeneratorForAnnotation<TestMetadata> {
  @override
  generateForAnnotatedElement(element, annotation, buildStep) {
    /// 生成以下程式
    return "class TestMetaDataFromGenerator{}";
  }
}

然後建立一個 Builder,用來告知 Build Runner 有哪些 Generator 需要去檢查,需要特別注意 Builder,是引用自 'package:build/build.dart',可不是我們平常使用的 material.dart。

現在支援的 Builder 有三種,分別是 PartBuilder、LibraryBuilder 和 SharedPartBuilder。簡單介紹一下:

  1. PartBuilder:
    PartBuilder 應該是我們最常會碰到的,如 json_annotation 生成的檔案。它用於生成部分代碼文件(通常是 .g.dart 文件)。當你看到如下註解時,這表示這個文件是由其他生成器產生的:

    part 'filename.g.dart';
    

    使用 PartBuilder 時,首先需要確定你的原始文件已經包含了這個 part 註解。然後使用 PartBuilder 定義你的生成器:

    import 'package:source_gen/source_gen.dart';
    import 'package:build/build.dart';
    
    Builder myBuilder(BuilderOptions options) =>
        PartBuilder([MyGenerator()], '.g.dart');
    
  2. LibraryBuilder:
    LibraryBuilder 用於生成一個完整的庫文件,而不是部分文件。當使用 LibraryBuilder 時,不需要 part 註解,因為它會生成一個完整的新文件。

    Builder myLibraryBuilder(BuilderOptions options) =>
        LibraryBuilder(MyGenerator(), generatedExtension: '.mygenerator.dart');
    
  3. SharedPartBuilder:
    SharedPartBuilder 允許多個生成器共享同一個部分文件。這在你希望將多個生成器的輸出放入同一個 .g.dart 文件時非常有用。

    Builder sharedBuilder(BuilderOptions options) =>
        SharedPartBuilder([MyGenerator()], 'shared_part');
    

這裡只做簡單的示範,所以選擇最容易做的 LibraryBuilder,並填入在上面完成的 TestGenerator 就完成摟!

import 'package:build/build.dart';
import 'package:flutter_day_10_build_runner/generator.dart';
import 'package:source_gen/source_gen.dart';

Builder testBuilder(options) => LibraryBuilder(TestGenerator());

終於來到最後一步配置,為了要跑動 Build Runner 我們必須要新增一個 build.yaml 到根目錄,然後填上相應的配置:

import 要根據上面的 testBuilder 的位置自行修改哦

builders:
  testBuilder:
    # builder 所在處
    import: 'package:your_package_name/builder.dart'
    # 有哪些 builder 方法
    builder_factories: ['testBuilder']
    # 生成的新文件后缀
    build_extensions: { '.dart': ['.g.part'] }
    auto_apply: root_package
    build_to: source

最後,只需要在命令行中運行以下命令,就可以看到我們自動化產生的程式碼摟:

flutter packages pub run build_runner build

完成後就能看到 source_gen 基於你的 Annotations 和生成器,為你生成 test.g.dart 文件 🎉

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// TestGenerator
// **************************************************************************

class TestMetaDataFromGenerator {}

到這邊相信大家已經對 source_gen 是如何運作的有了基礎的了解,我們再回到 TestMetaData 的部分,這次我們加上一點小變化,讓 TestMetaData 是可以加入參數的的。例如我們加上 className 這個參數。

class TestMetaData {
  final String className;
  const TestMetaData(this.className);
}

修改 test.dart,加上 className

import 'package:flutter_day_10_build_runner/test_annotation.dart';

@TestMetaData("doraralab")
class MyClass {}

這次我們要針對 generator 做一些修改,讓他可以去讀懂 TestMetaData,並取出 className 讓我們使用。這裡就來補充剛剛要補充的 element, annotation, buildStep。

  1. element:
    表示被註解的元素。我們可以通過 element.kind 知道被你註解的元素是 class 或是 function 甚至可能是 enum ,不管是哪一種都有對應的 kind。通過 element.displayName 可以拿到這個被註解元素的名稱,簡而言之你可以通過這個參數來訪問有關被註解元素的所有信息。

  2. buildStep:
    用來控制寫入與讀取檔案的功能,這部分比較少更動。

  3. annotation:
    這裡的 annotation 就是我們上面提供的 TestMetaData,不過我們沒辦法直接把他當成 class 的實體操作,而是要透過 .read 或是 .peek 的方法來拿到內部 parameter 的值。兩個不同是 .read 如果遇到不知道的參數會拋出錯誤,而 .peek 會返回 null,下面直接看我們要更改的程式碼。

import 'package:flutter_day_10_build_runner/test_annotation.dart';
import 'package:source_gen/source_gen.dart';

class TestGenerator extends GeneratorForAnnotation<TestMetaData> {
  @override
  generateForAnnotatedElement(element, annotation, buildStep) {
		// 用 peek 的話就要去 handle null 的情況,.read 則是可以直接噴錯讓 build runner 失敗
    return "class ${annotation.peek("className")?.stringValue ?? "Unknown"}{}";
  }
}

重新跑 flutter packages pub run build_runner build,就可以找到我們的 test.g.dart,成為我們想要的結果摟!

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// TestGenerator
// **************************************************************************

class doraralab {}

💡小提示:如果你是使用 vscode,想要 debug 整個 build runner 的過程,可以把下面這段加入到你的 launch.json,就可以瞜

{
  "name": "Debug  Generator",
  "request": "launch",
  "program": ".dart_tool/build/entrypoint/build.dart",
  "type": "dart",
  "args": ["build"]
},

結語:

代碼自動生成不僅可以為開發者省去大量的時間,更重要的是,它確保了代碼的一致性和品質。透過 source_gen 的使用,我們能更高效地完成日常的開發工作,並避免許多手工編寫代碼時可能出現的錯誤。希望這篇文章可以幫助你更深入地了解 Flutter 背後的工具與技巧,並激發你進一步探索其它強大的功能。未來的開發之路,願我們能更快、更好、更強!


上一篇
Day 9:@@ 這個是什麼?|Flutter MetaData
下一篇
Day 11:嗟乎!從外到內看 Flutter 渲染引擎|Skia → Impeller
系列文
Senior 工程師的入門指南:Flutter 進擊之路 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言